﻿/////////////////////////////////////////////////////////////////////////////////
//
//	vp_Utility.cs
//	© VisionPunk. All Rights Reserved.
//	https://twitter.com/VisionPunk
//	http://www.visionpunk.com
//
//	description:	miscellaneous utility functions
//
/////////////////////////////////////////////////////////////////////////////////

using UnityEngine;
using System.Reflection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;  

public static class Utilitys
{
	/// <summary>
	/// Performs a stack trace to see where things went wrong
	/// for error reporting.
	/// </summary>
	public static string GetErrorLocation(int level = 1, bool showOnlyLast = false)
	{

        System.Diagnostics.StackTrace stackTrace = new System.Diagnostics.StackTrace();
		string result = "";
		string declaringType = "";

		for (int v = stackTrace.FrameCount - 1; v > level; v--)
		{
			if (v < stackTrace.FrameCount - 1)
				result += " --> ";
            System.Diagnostics.StackFrame stackFrame = stackTrace.GetFrame(v);
			if (stackFrame.GetMethod().DeclaringType.ToString() == declaringType)
				result = "";	// only report the last called method within every class
			declaringType = stackFrame.GetMethod().DeclaringType.ToString();
			result += declaringType + ":" + stackFrame.GetMethod().Name;
		}

		if (showOnlyLast)
		{
			try
			{
				result = result.Substring(result.LastIndexOf(" --> "));
				result = result.Replace(" --> ", "");
			}
			catch
			{
			}
		}

		return result;

	}


	/// <summary>
	/// Returns the 'syntax style' formatted version of a type name.
	/// for example: passing 'System.Single' will return 'float'.
	/// </summary>
	public static string GetTypeAlias(Type type)
	{

		string s = "";

		if (!m_TypeAliases.TryGetValue(type, out s))
			return type.ToString();

		return s;

	}


	/// <summary>
	/// Dictionary of type aliases for error messages.
	/// </summary>
	private static readonly Dictionary<Type, string> m_TypeAliases = new Dictionary<Type, string>()
	{

		{ typeof(void), "void" },
		{ typeof(byte), "byte" },
		{ typeof(sbyte), "sbyte" },
		{ typeof(short), "short" },
		{ typeof(ushort), "ushort" },
		{ typeof(int), "int" },
		{ typeof(uint), "uint" },
		{ typeof(long), "long" },
		{ typeof(ulong), "ulong" },
		{ typeof(float), "float" },
		{ typeof(double), "double" },
		{ typeof(decimal), "decimal" },
		{ typeof(object), "object" },
		{ typeof(bool), "bool" },
		{ typeof(char), "char" },
		{ typeof(string), "string" },
		{ typeof(UnityEngine.Vector2), "Vector2" },
		{ typeof(UnityEngine.Vector3), "Vector3" },
		{ typeof(UnityEngine.Vector4), "Vector4" }

	};


	/// <summary>
	/// Activates or deactivates a gameobject for any Unity version.
	/// </summary>
	public static void Activate(GameObject obj, bool activate = true)
	{

#if UNITY_3_5
		obj.SetActiveRecursively(activate);
#else
		obj.SetActive(activate);
#endif

	}


	/// <summary>
	/// Returns active status of a gameobject for any Unity version.
	/// </summary>
	public static bool IsActive(GameObject obj)
	{

#if UNITY_3_5
		return obj.active;
#else
		return obj.activeSelf;
#endif

	}


	/// <summary>
	/// shows or hides the mouse cursor in a way suitable for the
	/// current unity version
	/// </summary>
	public static bool LockCursor
	{

        // compile only for unity 5+
        #if (!(UNITY_4_7 ||UNITY_4_6 || UNITY_4_5 || UNITY_4_3 || UNITY_4_2 || UNITY_4_1 || UNITY_4_0 || UNITY_3_5))
        get
		{
			return ((Cursor.lockState == CursorLockMode.Locked) ? true : false);
		}
		set
		{
			// toggling cursor visible and invisible is currently buggy in the Unity 5
			// editor so we need to toggle brute force with custom arrow art
			#if UNITY_EDITOR
				Cursor.SetCursor((value ? InvisibleCursor : VisibleCursor), Vector2.zero, CursorMode.Auto);
			#else
				// running in a build so toggling visibility should work fine
				Cursor.visible = !value;
			#endif
			Cursor.lockState = (value ? CursorLockMode.Locked : CursorLockMode.None);
		}
#else
		// compile only for unity 4.6 and older
		get { return Screen.lockCursor; }
		set { Screen.lockCursor = value; }
#endif

	}


// compile only for unity 5+ editor
#if UNITY_EDITOR && (!(UNITY_4_6 || UNITY_4_5 || UNITY_4_3 || UNITY_4_2 || UNITY_4_1 || UNITY_4_0 || UNITY_3_5))

	// properties for setting up cursor art in the Unity 5 editor
	// (see further comments in 'LockCursor')

	static Texture2D m_VisibleCursor = null;
	static Texture2D VisibleCursor
	{
		get
		{
			if (m_VisibleCursor == null)
				m_VisibleCursor = Resources.Load("Input/EditorCursorVisible") as Texture2D;
			return m_VisibleCursor;
		}
	}

	static Texture2D m_InvisibleCursor = null;
	static Texture2D InvisibleCursor
	{
		get
		{
			if (m_InvisibleCursor == null)
				m_InvisibleCursor = Resources.Load("Input/EditorCursorInvisible") as Texture2D;
			return m_InvisibleCursor;
		}
	}
#endif


	/// <summary>
	/// Returns a random object from a list.
	/// </summary>
	public static T RandomObject<T>(this List<T> list)
	{

		List<T> newList = new List<T>();
		newList.AddRange(list);
		newList.RandomizeList();
		return newList.FirstOrDefault();

	}
	

	/// <summary>
	/// Returns a list of the specified child components
	/// </summary>
	public static List<T> ChildComponentsToList<T>( this Transform t ) where T : Component
	{

		return t.GetComponentsInChildren<T>().ToList();

	}


	/// <summary>
	/// 
	/// </summary>
	public static bool IsDescendant(Transform descendant, Transform potentialAncestor)
	{

		if (descendant == null)
			return false;

		if (potentialAncestor == null)
			return false;

		if (descendant.parent == descendant)
			return false;

		if (descendant.parent == potentialAncestor)
			return true;

		return IsDescendant(descendant.parent, potentialAncestor);

	}



	/// <summary>
	/// if target is a transform, returns its parent. if not, returns its
	/// transform. will return null if:
	/// 1) target is null
	/// 2) target's transform is null (has somehow been deleted)
	/// 3) target transform's parent is null (we have hit the scene root)
	/// </summary>
	public static Component GetParent(Component target)
	{

		if (target == null)
			return null;

		if (target != target.transform)
			return target.transform;

		return target.transform.parent;

	}


	/// <summary>
	/// 
	/// </summary>
	public static Transform GetTransformByNameInChildren(Transform trans, string name, bool includeInactive = false, bool subString = false)
	{

		name = name.ToLower();

		foreach (Transform t in trans)
		{
			if(!subString)
			{
				if ((t.name.ToLower() == name) && ((includeInactive) || t.gameObject.activeInHierarchy))
				return t;
			}
			else
			{
				if ((t.name.ToLower().Contains(name)) && ((includeInactive) || t.gameObject.activeInHierarchy))
				return t;
			}

			Transform ct = GetTransformByNameInChildren(t, name, includeInactive, subString);
			if (ct != null)
				return ct;
		}

		return null;

	}


	/// <summary>
	/// 
	/// </summary>
	public static Transform GetTransformByNameInAncestors(Transform trans, string name, bool includeInactive = false, bool subString = false)
	{

		if (trans.parent == null)
			return null;

		name = name.ToLower();

		if(!subString)
		{
			if ((trans.parent.name.ToLower() == name) && ((includeInactive) || trans.gameObject.activeInHierarchy))
				return trans.parent;
		}
		else
		{
			if ((trans.parent.name.ToLower().Contains(name)) && ((includeInactive) || trans.gameObject.activeInHierarchy))
				return trans.parent;
		}

		Transform ct = GetTransformByNameInAncestors(trans.parent, name, includeInactive, subString);
		if (ct != null)
			return ct;

		return null;

	}

	/// <summary>
	/// Returns a positive integer value that is guaranteed to be unique
	/// until one billion IDs have been generated.
	/// </summary>
	public static int UniqueID
	{

		get
		{
			int i;
		reroll:
			i = UnityEngine.Random.Range(0, 1000000000);
			if (m_UniqueIDs.ContainsKey(i))	// likely won't happen (ever)
			{
				if (m_UniqueIDs.Count >= 1000000000)
				{
					ClearUniqueIDs();
					//LogSystem.Logger.Log("Warning (vp_Utility.UniqueID) More than 1 billion unique IDs have been generated. This seems like an awful lot for a game client. Clearing dictionary and starting over!");
				}
				goto reroll;
			}
			m_UniqueIDs.Add(i, 0);
			return i;
		}

	}
	private static Dictionary<int, int> m_UniqueIDs = new Dictionary<int, int>();


	/// <summary>
	/// clears all generated unique IDs
	/// </summary>
	public static void ClearUniqueIDs()
	{
		m_UniqueIDs.Clear();
	}


	/// <summary>
	/// generates an integer value based on a world position. this can
	/// be used to establish the same object IDs across clients without
	/// a lot of manual object ID assignment.
	/// NOTES:
	/// 1) this method should be run in Awake, before any object has
	/// had a chance to alter its start position
	/// 2) the blatant assumption here is that as long as every object
	/// using this method exists at a unique world coordinate on Awake
	/// - and this coordinate is the same on all clients - the IDs
	/// generated will be unique and deterministic. there may be some
	/// edge cases where the same IDs are generated but they should be
	/// very rare
	/// </summary>
	public static int PositionToID(Vector3 position)
	{

		return (int)Mathf.Abs(
			  (position.x * 10000)
			+ (position.y * 1000)
			+ (position.z * 100));

	}


    public static string GetMD5HashFromFile(string fileName)
    {
        try
        {
            FileStream file = new FileStream(fileName, FileMode.Open);
            System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
            byte[] retVal = md5.ComputeHash(file);
            file.Close();

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < retVal.Length; i++)
            {
                sb.Append(retVal[i].ToString("x2"));
            }
            return sb.ToString();
        }
        catch (Exception ex)
        {
            throw new Exception("GetMD5HashFromFile() fail,error:" + ex.Message);
        }
    }

    private static Dictionary<string, DateTime> m_times = new Dictionary<string, DateTime>();

    public static void DebugStar(string log)
    {
        m_times.Add(log, System.DateTime.Now);
    }

    public static void DebugEnd(string log)
    {
        float time = GetTime(log);
        //if (time > 50f)
        {
            Debug.Log("->>>  " + log + " cost:" + time + " mis");
        }
    }

    public static float GetTime(string log)
    {
        DateTime time;
        bool ret = m_times.TryGetValue(log, out time);
        float retf = -1f;
        if (ret)
        {
            float sec = System.DateTime.Now.Second - time.Second;
            float milsec = System.DateTime.Now.Millisecond - time.Millisecond;
            retf = (sec * 1000 + milsec);
        }
        return retf;
    }
}

